// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "Private.h"
#include "TfTextLayoutSink.h"
#include "SampleIME.h"
#include "GetTextExtentEditSession.h"

CTfTextLayoutSink::CTfTextLayoutSink(_In_ CSampleIME* pTextService) {
	_pTextService = pTextService;
	_pTextService->AddRef();

	_pRangeComposition = nullptr;
	_pContextDocument = nullptr;
	_tfEditCookie = TF_INVALID_EDIT_COOKIE;

	_dwCookieTextLayoutSink = TF_INVALID_COOKIE;

	_refCount = 1;

	DllAddRef();
}

CTfTextLayoutSink::~CTfTextLayoutSink() {
	if (_pTextService) {
		_pTextService->Release();
	}

	DllRelease();
}

STDAPI CTfTextLayoutSink::QueryInterface(REFIID riid, _Outptr_ void** ppvObj) {
	if (ppvObj == nullptr) {
		return E_INVALIDARG;
	}

	*ppvObj = nullptr;

	if (IsEqualIID(riid, IID_IUnknown) ||
		IsEqualIID(riid, IID_ITfTextLayoutSink)) {
		*ppvObj = (ITfTextLayoutSink*)this;
	}

	if (*ppvObj) {
		AddRef();
		return S_OK;
	}

	return E_NOINTERFACE;
}

STDAPI_(ULONG) CTfTextLayoutSink::AddRef() {
	return ++_refCount;
}

STDAPI_(ULONG) CTfTextLayoutSink::Release() {
	LONG cr = --_refCount;

	assert(_refCount >= 0);

	if (_refCount == 0) {
		delete this;
	}

	return cr;
}

//+---------------------------------------------------------------------------
//
// ITfTextLayoutSink::OnLayoutChange
//
//----------------------------------------------------------------------------

STDAPI CTfTextLayoutSink::OnLayoutChange(_In_ ITfContext* pContext, TfLayoutCode lcode, _In_ ITfContextView* pContextView) {
	// we're interested in only document context.
	if (pContext != _pContextDocument) {
		return S_OK;
	}

	switch (lcode) {
	case TF_LC_CHANGE:
	{
		CGetTextExtentEditSession* pEditSession = nullptr;
		pEditSession = new (std::nothrow) CGetTextExtentEditSession(_pTextService, pContext, pContextView, _pRangeComposition, this);
		if (nullptr != (pEditSession)) {
			HRESULT hr = S_OK;
			pContext->RequestEditSession(_pTextService->_GetClientId(), pEditSession, TF_ES_SYNC | TF_ES_READ, &hr);

			pEditSession->Release();
		}
	}
	break;

	case TF_LC_DESTROY:
		_LayoutDestroyNotification();
		break;

	}
	return S_OK;
}

HRESULT CTfTextLayoutSink::_StartLayout(_In_ ITfContext* pContextDocument, TfEditCookie ec, _In_ ITfRange* pRangeComposition) {
	_pContextDocument = pContextDocument;
	_pContextDocument->AddRef();

	_pRangeComposition = pRangeComposition;
	_pRangeComposition->AddRef();

	_tfEditCookie = ec;

	return _AdviseTextLayoutSink();
}

VOID CTfTextLayoutSink::_EndLayout() {
	if (_pRangeComposition) {
		_pRangeComposition->Release();
		_pRangeComposition = nullptr;
	}

	if (_pContextDocument) {
		_UnadviseTextLayoutSink();
		_pContextDocument->Release();
		_pContextDocument = nullptr;
	}
}

HRESULT CTfTextLayoutSink::_AdviseTextLayoutSink() {
	HRESULT hr = S_OK;
	ITfSource* pSource = nullptr;

	hr = _pContextDocument->QueryInterface(IID_ITfSource, (void**)&pSource);
	if (FAILED(hr)) {
		return hr;
	}

	hr = pSource->AdviseSink(IID_ITfTextLayoutSink, (ITfTextLayoutSink*)this, &_dwCookieTextLayoutSink);
	if (FAILED(hr)) {
		pSource->Release();
		return hr;
	}

	pSource->Release();

	return hr;
}

HRESULT CTfTextLayoutSink::_UnadviseTextLayoutSink() {
	HRESULT hr = S_OK;
	ITfSource* pSource = nullptr;

	if (nullptr == _pContextDocument) {
		return E_FAIL;
	}

	hr = _pContextDocument->QueryInterface(IID_ITfSource, (void**)&pSource);
	if (FAILED(hr)) {
		return hr;
	}

	hr = pSource->UnadviseSink(_dwCookieTextLayoutSink);
	if (FAILED(hr)) {
		pSource->Release();
		return hr;
	}

	pSource->Release();

	return hr;
}

HRESULT CTfTextLayoutSink::_GetTextExt(_Out_ RECT* lpRect) {
	HRESULT hr = S_OK;
	BOOL isClipped = TRUE;
	ITfContextView* pContextView = nullptr;

	hr = _pContextDocument->GetActiveView(&pContextView);
	if (FAILED(hr)) {
		return hr;
	}

	if (FAILED(hr = pContextView->GetTextExt(_tfEditCookie, _pRangeComposition, lpRect, &isClipped))) {
		return hr;
	}

	pContextView->Release();

	return S_OK;
}
